package com.yhb.service.impl;

import com.yhb.conf.ResultCode;
import com.yhb.controller.form.rpc.RpcUser;
import com.yhb.controller.form.rpc.RpcResult;
import com.yhb.dao.CpsMapper;
import com.yhb.model.CpsLevel;
import com.yhb.service.CpsService;
import com.yhb.service.UserService;
import com.yhb.utils.DateUtils;
import com.yhb.utils.SignConfig;
import lombok.Builder;
import lombok.Data;
import lombok.NonNull;
import lombok.extern.log4j.Log4j;
import lombok.var;
import me.edzh.spring.bootstrap.model.Tuple;
import me.edzh.spring.bootstrap.processor.SignAndEncryptProcessor;
import me.edzh.spring.bootstrap.utils.JSONUtil;
import me.edzh.spring.bootstrap.utils.ObjectMapperFactory;
import me.edzh.spring.bootstrap.utils.exception.ServiceException;
import me.edzh.spring.bootstrap.utils.http.ClientHolder;
import me.edzh.spring.bootstrap.utils.security.AESUtils;
import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.RequestBody;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.util.*;


@Service
@Log4j
public class CpsServiceImpl implements CpsService {

    @Autowired
    private UserService userService;

    @Autowired
    private CpsMapper cpsMapper;

    @Value("${rpc.cpsIp}")
    private String cpsServerIp;

    @Value("${rpc.aesKey}")
    private String aesKeyString;

    @Value("${rpc.aesAlgorithm}")
    private String aesAlgorithm;

    @Value("${rpc.aesIv}")
    private String aesIvString;

    @Autowired
    private SignConfig signConfig;

    @Value("${CPS_AVAILABLE}")
    private String cpsAvailable;

    private Key aesKey;

    private IvParameterSpec aesIv;

    @PostConstruct
    private void init() {
        byte[] raw = Base64.getDecoder().decode(aesKeyString);
        aesKey = new SecretKeySpec(raw, aesAlgorithm);
        aesIv = new IvParameterSpec(aesIvString.getBytes());
    }


    @Override
    public void calcTreeActiveCount(String rootUserId, Date startTime, Date endTime) {
        calcGroupActiveCount(rootUserId, new VipLevelCounter(0), startTime, endTime);
    }

    private Tuple<Long, Long> calcGroupActiveCount(String rootUserId, @NonNull VipLevelCounter counter, Date startTime, Date endTime) {
        Long activeCount = 0L;
        Long followerCount = 0L;

        var ids = userService.getInviteeIds(rootUserId);

        for (var id : ids) {
            var t = calcGroupActiveCount(id, counter, startTime, endTime);
            activeCount += t.getValue1();
            followerCount += t.getValue2();
        }

        followerCount += ids.size();

        var vipLevel = VipLevelCounter.getRawVipLevel(activeCount, counter.getVip5Count());
        if (vipLevel >= 5) {
            counter.setVip5Count(counter.getVip5Count() + 1);
        }

        log.warn("[ActiveCount][" + rootUserId + "][" + activeCount + "][" + vipLevel + "]");
        var level = CpsLevel.builder()
                .userId(rootUserId)
                .activeCount(activeCount)
                .level((long)vipLevel)
                .vip5Count((long)counter.getVip5Count())
                .followersCount(followerCount)
                .build();

        cpsMapper.store(level);
        cpsMapper.storeFlow(level, DateUtils.getDateString(endTime));

        activeCount += userService.isActive(rootUserId, startTime, endTime); // 20200522 活跃粉丝计算不包含自己

        return new Tuple<>(activeCount, followerCount);

    }

    @Override
    public CpsLevel getCpsLevel(String userId) {
        return ObjectUtils.defaultIfNull(cpsMapper.get(userId), new CpsLevel());
    }

    @Override
    public CpsLevel getCpsLevelInDate(String userId, String dateString) {
        return cpsMapper.getLevelInDate(userId, dateString);
    }


    @Override
    public RpcUser loginCps(String userId) {
        if (!"true".equals(cpsAvailable)) {
            return new RpcUser();
        }

        return queryCps("/cps/api/yhb/auth/login", new HashMap<String, Object>(){{
            put("yhbId", userId);
        }});
    }

    @Override
    public RpcUser getCpsUserInfo(String userId) {
        if (!"true".equals(cpsAvailable)) {
            return new RpcUser();
        }

        return queryCps("/cps/api/yhb/auth/info", new HashMap<String, Object >() {{
            put("yhbId", userId);
        }});
    }

    private RpcUser queryCps(String url, Map<String, Object> params) {
        params.put("timeStamp", new Date().getTime() + "");
        String sign = signConfig.sign(SignAndEncryptProcessor.getParamSignString(params));

        var client = ClientHolder.getClient();
        try {
            String base64Body = Base64.getEncoder().encodeToString(AESUtils.encrypt(
                    aesKey,
                    ObjectMapperFactory
                            .getObjectMapper()
                            .writeValueAsString(params)
                            .getBytes(),
                    aesAlgorithm,
                    aesIv));


            RequestBody body = RequestBody.create(base64Body, MediaType.parse("application/json;charset-utf8"));

            var req = new Request.Builder()
                    .url(cpsServerIp + url)
                    .post(body)
                    .header("SIGN", sign)
                    .header("CONTENT-TYPE", "application/json;charset-utf8")
                    .build();

            var resp = client.newCall(req).execute();

            var resultJson = resp.body().string();
            log.warn("cps backend resp:\n" + resultJson);
            var result = JSONUtil.parseJson(resultJson, RpcResult.class);

            if (result.getStatus() != 1L) {
                log.error(result.getMessage());
                throw new RuntimeException();
            }



            return JSONUtil.parseJson(result.getData(), RpcUser.class);
        } catch (Exception e) {
            throw new ServiceException(ResultCode.ERROR_PARAMETER_WRONG, "request to back err", e);
        }


    }



    @Data
    @Builder
    private static class VipLevelCounter {
        @Builder.Default
        private int vip5Count = 0;

        /**
         * 仅返回和活跃用户数的对应关系, v5数量需要计数
         *
         * @param activeCount
         * @return
         */
        public static Integer getRawVipLevel(Long activeCount, int vip5Count) {
            if (activeCount == null || activeCount < 50) {
                return 1;
            }

            if (activeCount < 800) {
                return 2;
            }

            if (activeCount < 1500) {
                return 3;
            }

            if (activeCount < 3000) {
                return 4;
            }

            if (activeCount < 5000) {
                return 5;
            }

            if (activeCount < 10000) {
                if (vip5Count < 1) return 5;
                return 6;
            }

            if (activeCount < 30000) {
                if (vip5Count < 2) return 5;
                return 7;
            }

            if (activeCount < 70000) {
                if (vip5Count < 3) return 5;
                return 8;
            }

            if (vip5Count < 5) return 5;

            return 9;
        }
    }
}
